package graphics.rendering.shaders;

import org.lwjgl.opengl.ARBFragmentShader;
import org.lwjgl.opengl.ARBShaderObjects;
import org.lwjgl.opengl.ARBVertexShader;
import org.lwjgl.opengl.GL11;

/**
 * Encapsulates an OpenGL shader program.
 * @author Joel Rausch
 */
public class ShaderProgram {

	/** Shader program ID. */
	private final int shaderProgId;
	
	/** Vertex shader ID */
	private final int vertShaderId;
	
	/** Fragment shader ID */
	private final int fragShaderId;
	
	public ShaderProgram(String vertexSource, String fragmentSource) throws Exception {
		// Create shader program object
		shaderProgId = ARBShaderObjects.glCreateProgramObjectARB();
		if(shaderProgId == 0) 
			throw new Exception("Failed to create shader object");
		
		// Create vertex and fragment shader objects
		vertShaderId = ARBShaderObjects.glCreateShaderObjectARB(ARBVertexShader.GL_VERTEX_SHADER_ARB);
		fragShaderId = ARBShaderObjects.glCreateShaderObjectARB(ARBFragmentShader.GL_FRAGMENT_SHADER_ARB);
		if((vertShaderId & fragShaderId) == 0) 
			throw new Exception("");
		if(vertShaderId == 0)
			throw new Exception("Failed to create vertex shader object");
		if(fragShaderId == 0)
			throw new Exception("Failed to create fragment shader object");
		
		// Compile shader objects
		ARBShaderObjects.glShaderSourceARB(vertShaderId, vertexSource);
		ARBShaderObjects.glShaderSourceARB(fragShaderId, fragmentSource);
		ARBShaderObjects.glCompileShaderARB(vertShaderId);
		ARBShaderObjects.glCompileShaderARB(fragShaderId);
		
		// Check shader compile status
		if(ARBShaderObjects.glGetObjectParameteriARB(vertShaderId, ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB) == GL11.GL_FALSE)
			throw new Exception("Vertex shader compile error");
		if(ARBShaderObjects.glGetObjectParameteriARB(fragShaderId, ARBShaderObjects.GL_OBJECT_COMPILE_STATUS_ARB) == GL11.GL_FALSE)
			throw new Exception("Fragment shader compile error");
		
		// Link shader program
		ARBShaderObjects.glAttachObjectARB(shaderProgId, vertShaderId);
		ARBShaderObjects.glAttachObjectARB(shaderProgId, fragShaderId);
		ARBShaderObjects.glLinkProgramARB(shaderProgId);
		if(ARBShaderObjects.glGetObjectParameteriARB(shaderProgId, ARBShaderObjects.GL_OBJECT_LINK_STATUS_ARB) == GL11.GL_FALSE) {
			throw new Exception("Failed to link shader program");
		}
		
		// Validate shader program
		ARBShaderObjects.glValidateProgramARB(shaderProgId);
		if(ARBShaderObjects.glGetObjectParameteriARB(shaderProgId, ARBShaderObjects.GL_OBJECT_VALIDATE_STATUS_ARB) == GL11.GL_FALSE) {
			throw new Exception("Shader program failed validation");
		}
	}
	
	/** Destroy and clean up shader program. */
	public void destroy() {
		unbindShader();
		//TODO Destroy shader
	}
	
	/**
	 * Make this the current shader program.
	 */
	public void bindShader() {
		ARBShaderObjects.glUseProgramObjectARB(shaderProgId);
	}
	
	/**
	 * Make no shader program current.
	 */
	public static void unbindShader() {
		ARBShaderObjects.glUseProgramObjectARB(0);
	}
}
